package android.user.rmc;


import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.matrix.Matrix;
import android.matrix.RemoteMatrix;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.remotematrixclient.objclient.ObjClient;


import android.widget.ProgressBar;
import android.widget.TextView;



/**
 * Esta es la actividad principal de la aplicación 
 * <b>RemoteMatrixCalculator</b>. La aplicación consta de dos partes. La
 * primera es la plantilla gráfica que muestra los resultados de las 
 * operaciones realizadas. La segunda parte consta de un hilo que ejecuta las 
 * operaciones tras bastidores. 
 * 
 * El frente es bastante simple utilizando un texto y una barra de progreso.
 * 
 * Existen tres clases privadas que son miembros de esta actividad, la primera
 * es una subclase de <code>AsyncTask</code>, llamada <code>MainTask</code>
 * se encarga de la ejecución de las tareas propuestas para la aplicación. 
 * 
 * Esta acepta objetos de tipo <code>Task</code>, la cual es una interfase que 
 * define los métodos que se llaman en <code>MainTask</code> para realizar las 
 * tareas planeadas. Dos clases implementan esta interfase:
 * 
 * <code>LocalTask</code> - realiza una multiplicación de matrices utilizando
 * la clase <code>Matrix</code>.
 * <code>RemoteTask</code> - realiza una multiplicación de matrices utilizando
 * la clase <code>RemoteMatrix</code>.
 * 
 * @author Rafael Asprilla
 *
 */
public class RemoteMatrixCalculatorActivity extends Activity 
{
	/**
	 * Cuadro de texto para presentar los datos.
	 */
	static TextView txt;
	
	/**
	 * Barra de progreso que muestra el avance de la aplicación.
	 */
	static ProgressBar pb;
	
	/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        txt = (TextView) this.findViewById(R.id.Text1);
        pb = (ProgressBar) this.findViewById(R.id.progressBar1);
        
        //ObjClient.init("192.168.1.10", 3000);
		ObjClient.init("192.168.46.100", 3000);
        	
        while(ObjClient.getInstance().max_cpus()  == 0);
        
        Task[] task = new Task[300];
        
        for(int i = 0; i < 50; i++){
        	task[i] = new RemoteTask((i+1)*10, 3);
        	task[i+50] = new RemoteTask((i+1)*10, 5);
        	task[i+100] = new RemoteTask((i+1)*10, 7);
        	task[i+150] = new RemoteTask((i+1)*10, 9);
        	task[i+200] = new RemoteTask((i+1)*10, 11);
        	task[i+250] = new LocalTask((i+1)*10);
        }
        
        new MainTask("remote5prc.csv", 5).execute(task); 
    }

    /**
     * Sale de la aplicación.
     */
    private void finishthis(){
    	System.exit(0);
    }
    
	/**
	 * <code>MainTask</code> es una subclase de <code>AsyncTask</code> que le
	 * permite a <b>RemoteMatrixCalulator</b> ejecutar multiplicaciones de 
	 * matrices, evita blockear la ejecución de Android. Corre como un hilo que
	 * puede acceder al contexto de la actividad principal.
	 * 
	 * @author Rafael Asprilla
	 */
	private class MainTask extends AsyncTask<Task, String, Void>{

		/**
		 * Progreso de la aplicación ejecutando las tareas asignadas.
		 * El valor va de 0 a 100.
		 */
		private int progress = 0;
		
		/**
		 * Define cuantas veces debe repetirse cada tarea
		 */
		private int repeat;
		
		/**
		 * Nombre del archivo de bitácora donde se registran los resultados.
		 */
		private String filename;


		/**
		 * Constructor de la clase <code>MainTask</code>
		 * 
		 * @param filename
		 * Nombre del archivo de registro de resultados.
		 * @param repeat
		 * Define el número de repeticiones para cada operación.
		 */
		public MainTask(String filename, int repeat){
			this.filename = filename;
			this.repeat = repeat;
		}

		/* (non-Javadoc)
		 * @see android.os.AsyncTask#doInBackground(Params[])
		 */
		@Override
		protected Void doInBackground(Task... arg0) 
		{
			for(int j = 0; j < arg0.length; j++){
				this.publishProgress(arg0[j].getSize());
				for(int i = 0; i < repeat; i++){
				
					arg0[j].run();
					this.publishProgress(arg0[j].getResult()+ " ");
					progress = (int)((j*repeat+i)*100.0/(arg0.length*repeat));
				}
				
				this.publishProgress("\n");
			}
			ObjClient.getInstance().close();
			return null;
		}

		/* (non-Javadoc)
		 * @see android.os.AsyncTask#onProgressUpdate(Progress[])
		 */
		@Override
		protected void onProgressUpdate(String... values) {
			FileWriter out;
			pb.setProgress(progress);
			txt.append(values[0]);
			File file = new File(Environment.getExternalStorageDirectory()+File.separator, filename);
			try {
				if(file.exists())
					out = new FileWriter(file, true);
				else{
					out = new FileWriter(file);
				}
				
				out.append(values[0]);
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		/* (non-Javadoc)
		 * @see android.os.AsyncTask#onPostExecute(java.lang.Object)
		 */
		@Override
		protected void onPostExecute(Void result) {
			ObjClient.getInstance().FIN();
			finishthis();
			super.onPostExecute(result);
		}
    }
	
	/**
	 * Interfase que define las funciones utilizadas por <code>MainTask</code>
	 * para la ejecución y registro de las tareas programadas.
	 * 
	 * @author Rafael Asprilla
	 */
	private interface Task extends Runnable{
		
		/**
		 * @return
		 * Devuelve una cadena con el tamaño de la matriz y la cantidad de 
		 * operaciones que deben realizarse en la multiplicación.
		 */
		public abstract String getSize();
		
		/**
		 * @return
		 * Devuelve una cadena con el tiempo de ejecución de las operaciones.
		 */
		public abstract String getResult();
	}
	
	/**
	 * Define una tarea que realiza una multiplicación de matrices localmente.
	 * 
	 * @author Rafael Asprilla
	 *
	 */
	@SuppressLint("DefaultLocale")
	private class LocalTask implements Task{

		/**
		 * Tamaño de la matriz cuadrada.
		 */
		private int size;
		
		/**
		 * Tiempo de ejecución en milisegundos de cada etapa de la operación.
		 */
		private long op1, op2, op3, tot;
		
		/**
		 * Cadena con el resultado de la operación.
		 */
		private String result;
		
		/* (non-Javadoc)
		 * @see android.user.rmc.RemoteMatrixCalculatorActivity.Task#getSize()
		 */
		public String getSize(){
			return String.format("local; 1; %d; %d; ", size, size*size*(size +1));
		}
		
		/**
		 * Constructor de la clase <code>LocalTask</code>.
		 * 
		 * @param size
		 * Tamaño de la matriz cuadrada que se utilizará para la operación.
		 */
		public LocalTask(int size){
			this.size = size;
		}
		
		/* (non-Javadoc)
		 * @see android.user.rmc.RemoteMatrixCalculatorActivity.Task#getResult()
		 */
		@Override
		public String getResult(){
			result = String.format("%.3f; %.3f; %.3f; 0.000; %.3f;", op1/1000.0, op2/1000.0, op3/1000.0, tot/1000.0);
			return result;
		}
		
		/* (non-Javadoc)
		 * @see java.lang.Runnable#run()
		 */
		@SuppressWarnings("unused")
		@Override
		public void run() {
			long start = System.currentTimeMillis();
			Matrix a = Matrix.U(size, size);
			op1 = System.currentTimeMillis();
			Matrix b = Matrix.U(size, size);
			op2 = System.currentTimeMillis();
			Matrix c = a.mult(b);
			op3 = System.currentTimeMillis();
			tot = op3 - start;
			op3 = (op3 - op2);
			op2 = (op2 - op1);
			op1 = (op1 - start);
		}

	}
	
	/**
	 * Define una tarea de multiplicación de matrices remotas.
	 * 
	 * @author Rafael Asprilla
	 */
	@SuppressLint("DefaultLocale")
	private class RemoteTask implements Task{
		
		/**
		 * Tamaño de la matriz cuadrada.
		 */
		private int size;
		
		/**
		 * Tiempo de ejecución en milisegundos de cada etapa de la operación.
		 */
		private long op1, op2, op3, op4, tot;
		
		/**
		 * Cadena con el resultado de la operación.
		 */
		private String result;
		
		/**
		 * Número de unidades de procesamiento solicitadas al servidor remoto.
		 */
		private int cpus;
		
		/**
		 * Constructor de la clase <code>RemoteTask</code>.
		 * 
		 * @param size
		 * Define el tamaño de la matriz cuadrada que se utiliza para la 
		 * operación.
		 * @param cpus
		 * Define el número de procesadores que se requieren para esta
		 * operación.
		 */
		public RemoteTask(int size, int cpus){
			this.size = size;
			this.cpus = cpus;
		}
		
		/* (non-Javadoc)
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run() {
			if(cpus != ObjClient.getInstance().get_cpus())
				ObjClient.getInstance().set_cpus(cpus);
			
			long start_a = System.currentTimeMillis();
			RemoteMatrix a = RemoteMatrix.U(size, size);
			long start_b = System.currentTimeMillis();
			RemoteMatrix b = RemoteMatrix.U(size, size);
			while(!a.isDone());
			op1 = System.currentTimeMillis();
			while(!b.isDone());
			op2 = System.currentTimeMillis();
			RemoteMatrix c = a.mult(b);
			while(!a.isDone());
			op3 = System.currentTimeMillis();
			while(!c.isDone());
			op4 = System.currentTimeMillis();
			tot = op4 - start_a;
			op4 = op4 - op3;
			op3 = op3 - op2;
			op2 = op2 - start_b;
			op1 = op1 - start_a;	
		}

		/* (non-Javadoc)
		 * @see android.user.rmc.RemoteMatrixCalculatorActivity.Task#getSize()
		 */
		@Override
		public String getSize() {
			return String.format("remote; %d; %d; %d; ", cpus, size, size*size*(size +1));
		}

		/* (non-Javadoc)
		 * @see android.user.rmc.RemoteMatrixCalculatorActivity.Task#getResult()
		 */
		@Override
		public String getResult() {
			result = String.format("%.3f; %.3f; %.3f; %.3f; %.3f;", op1/1000.0, op2/1000.0, op3/1000.0, op4/1000.0, tot/1000.0);
			return result;
		}
		
	}
}

